-- Pefectly harmless gnome
-- SENT by Teta_Bonita

AddCSLuaFile( 'cl_init.lua' )
AddCSLuaFile( 'shared.lua' )
include('shared.lua')

local COOLDOWN_TIME = 1.5
local THINK_DELAY = 0.2

local TELEPORT_PROB = 0.09
local TELEPORT_COOLDOWN = 1
local TELEPORT_MAX_DIST = 3000
local TELEPORT_MAX_DIST_FROM_PLR = 350
local TELEPORT_MIN_DIST_FROM_PLR = 50
local TELEPORT_MAX_TRIES = 20

local CREEP_PROB = 0.09
local CREEP_COOLDOWN = 1
local CREEP_DISTANCE_FRACTION = 0.5

local STAB_MAX_DIST = 80
local STAB_PROB = 0.1
local STAB_COOLDOWN = 2
local STAB_FORCE = 12000
local STAB_DAMAGE = 500

local EMITSOUND_MAX_DIST = 2200
local EMITSOUND_PROB = 0.08
local EMITSOUND_COOLDOWN = 4

local CREEPY_SOUNDS = 
{

	{
		Snd = Sound( "k_lab.teleport_breathing" ),
		Dur = 6
	},
	
	{
		Snd = Sound( "d3_citadel.stalker_breathing" ),
		Dur = 5
	},
	
	{
		Snd = Sound( "ravenholm.madlaugh03" ),
		Dur = 4
	},
	
	{
		Snd = Sound( "ravenholm.madlaugh04" ),
		Dur = 2
	},
	
	{
		Snd = Sound( "npc_citizen.behindyou01" ),
		Dur = 1
	},
	
	{
		Snd = Sound( "npc_citizen.behindyou02" ),
		Dur = 1
	},

	{
		Snd = Sound( "npc_citizen.excuseme02" ),
		Dur = 1
	},
	
	{
		Snd = Sound( "Trainride.gman_riseshine" ),
		Dur = 6
	},
	
	{
		Snd = Sound( "song_d1_canals_13_helidead" ),
		Dur = 6
	},
	
	{
		Snd = Sound( "song_radio1" ),
		Dur = 39
	},
	
	{
		Snd = Sound( "d1_trainstation.playground_memory" ),
		Dur = 11
	},
	
	{
		Snd = Sound( "k_lab.kl_fiddlesticks" ),
		Dur = 2
	},
	
	{
		Snd = Sound( "k_lab.kl_ohdear" ),
		Dur = 1
	}

}

local sndDrawKnife = Sound( "weapons/knife/knife_deploy1.wav" )
local sndStab = Sound( "weapons/knife/knife_stab.wav" )


function ENT:SpawnFunction( plr, tr )

	if not tr.Hit then return end
	
	local ent = ents.Create( self.Classname )
	ent:SetPos( tr.HitPos + tr.HitNormal * 24 )
	ent:Spawn()
	ent:Activate()	
	ent:SetVictim( plr )
	
	return ent

end


function ENT:Initialize()	

	-- <3 companion cube
	self:SetModel( "models/props_junk/gnome.mdl" )
	self:SetSkin( 1 )
	
	self:PhysicsInit( SOLID_VPHYSICS )

	local phys = self:GetPhysicsObject()  	
	if phys:IsValid() then  		
		phys:Wake()  	
	end

	self.Width = self:BoundingRadius() * 0.5
	
	self.CurSound = ""
	
	self.NextTeleport = 0
	self.NextCreep = 0
	self.NextEmit = 0
	self.NextStab = 0
	
	self.Knife = NULL
	
	self.IsBeingCreepy = false
	
end


function ENT:SetVictim( plr )

	self.Victim = plr

end


function ENT:HasVictimLOS()

	local tr = util.TraceLine(
	{
		start 	= self:GetPos(),
		endpos 	= self.Victim:LocalToWorld( self.Victim:OBBCenter() ),
		filter 	= { self, self.Kinfe },
		mask 	= CONTENTS_SOLID | CONTENTS_OPAQUE | CONTENTS_MOVEABLE
	} )
	
	if tr.Fraction > 0.98 then return true end
	
	return false

end


function ENT:CanVictimSeeUs()

	local ViewEnt = self.Victim:GetViewEntity() -- In case he's looking through a camera or something

	if ViewEnt == self.Victim and not self:HasVictimLOS() then return false end

	local fov = self.Victim:GetFOV()
	local Disp = self:GetPos() - ViewEnt:GetPos()
	local Dist = Disp:Length()
	
	local MaxCos = math.abs( math.cos( math.acos( Dist / math.sqrt( Dist * Dist + self.Width * self.Width ) ) + fov * ( math.pi / 180 ) ) )
	Disp:Normalize()

	if Disp:Dot( ViewEnt:EyeAngles():Forward() ) > MaxCos then
		return true
	end
	
	return false

end


function ENT:DrawKnife( vec )
	
	vec.z = 0
	vec:Normalize()
	local knifepos = self:GetPos() + vec * self.Width + Vector( 0, 0, 0.5 * self.Width )

	self.Knife = ents.Create( "prop_physics" )
	self.Knife:SetPos( knifepos )
	self.Knife:SetModel( "models/weapons/w_knife_t.mdl" )
	self.Knife:SetAngles( vec:Angle() )
	self.Knife:Spawn()
	self.Knife:SetParent( self )
	
	self:EmitSound( sndDrawKnife, 300, 100 )

end


function ENT:TeleportToPos( pos )

	-- Keep checking positions until we find an emtpy one
	for i=1, TELEPORT_MAX_TRIES do

	local tr = util.TraceLine(
	{
		start 	= pos,
		endpos 	= pos - Vector( 0, 0, 64),
		filter 	= { self, self.Kinfe },
		mask 	= MASK_NPCSOLID
	} )
	
		local spawnpos = tr.HitPos + Vector( 0, 0, self.Width + 8 )
		
		if util.PointContents( spawnpos ) & MASK_SOLID == 0 then
		
			self:SetPos( spawnpos )
			
			-- Jostle it a bit in case it's stuck
			local phys = self:GetPhysicsObject()
			phys:ApplyForceCenter( VectorRand() * 2 )
			break
		
		end
		
		pos = pos + Vector( math.Rand( -20, 20 ), math.Rand( -20, 20 ), math.Rand( -5, 5 ) )
	
	end


end


function ENT:TeleportBehindVictim()

	if CurTime() < self.NextTeleport then return end

	local plraim = self.Victim:GetAimVector()
	plraim.z = 0
	plraim:Normalize()
	
	
	local plrpos = self.Victim:GetShootPos()
	local tr = util.TraceLine(
	{
		start 	= plrpos - plraim * TELEPORT_MIN_DIST_FROM_PLR,
		endpos 	= plrpos - plraim * TELEPORT_MAX_DIST_FROM_PLR,
		filter 	= { self, self.Kinfe, self.Victim },
		mask 	= MASK_NPCSOLID
	} )
	
	
	self:TeleportToPos( tr.HitPos + tr.HitNormal * self.Width )

	self.NextTeleport = CurTime() + TELEPORT_COOLDOWN

end


function ENT:CreepForward( disp )

	if CurTime() < self.NextCreep then return end

	self:TeleportToPos( self:GetPos() + disp * CREEP_DISTANCE_FRACTION )
	
	self.NextCreep = CurTime() + CREEP_COOLDOWN

end


function ENT:EmitCreepySounds()
	
	if CurTime() < self.NextEmit then return end
	
	local tSnd = CREEPY_SOUNDS[ math.random( 1, #CREEPY_SOUNDS ) ]

	self:EmitSound( tSnd.Snd, 600, 100 )
	self.CurSound = tSnd.Snd
	
	self.NextEmit = CurTime() + EMITSOUND_COOLDOWN + tSnd.Dur

end


function ENT:StabbyLunge( vec )

	if CurTime() < self.NextStab then return end
	
	local phys = self:GetPhysicsObject()
	local stabvec = vec * STAB_FORCE + Vector( 0, 0, 0.4 * STAB_FORCE )
	phys:ApplyForceCenter( stabvec )
	
   local vicpos = self.Victim:GetShootPos()
   

	self.Victim:TakeDamage( STAB_DAMAGE, self, self.Knife )
	
	self:EmitSound( sndStab, 500, 100 )

	self.NextStab = CurTime() + STAB_COOLDOWN

end


function ENT:KillSounds()

	self:StopSound( self.CurSound )

end


function ENT:PlayDead()

	if not self.IsBeingCreepy then return end
	
	-- Act normal!
	self.IsBeingCreepy = false

	-- Be quiet!
	self:KillSounds()
	
	-- Hide your knife!
	if self.Knife:IsValid() then self.Knife:Remove() end
	
	-- Reset everything since we stopped doing them
	self.NextTeleport = 0
	self.NextCreep = 0
	self.NextEmit = 0
	self.NextStab = 0

end


function ENT:BeCreepy()

	self.IsBeingCreepy = true

	-- Oh no, I already calculated these this frame.  I guess that makes my code innefficient.  Woe is me.
	local Disp = self.Victim:GetShootPos() - self:GetPos()
	local Dist = Disp:Length()
	local Vec = Disp:GetNormalized()
	
	-- Randomly decide what creepy things we should do
	local randnum = math.random()
	
	if Dist > TELEPORT_MAX_DIST or not self:HasVictimLOS() then -- We're too far away or we don't have line of sight. Teleport behind our victim.
	
		if TELEPORT_PROB > randnum then
		
			self:TeleportBehindVictim()
		
		end
	
	elseif Dist > EMITSOUND_MAX_DIST then -- We're too far away.  Start creeping towards our victim...

		if CREEP_PROB > randnum then
		
			self:CreepForward( Disp )
		
		end
	
	elseif Dist > STAB_MAX_DIST then -- We're close enough to start making disturbing noises as well.
	
		if CREEP_PROB > randnum then
		
			self:CreepForward( Disp )
		
		elseif CREEP_PROB + EMITSOUND_PROB > randnum then
		
			self:EmitCreepySounds()
		
		end
		
	else -- We're close enough to attack! mwahahahaha!
	
		if not self.Knife:IsValid() then
			self:DrawKnife( Vec )
		end
	
		if EMITSOUND_PROB > randnum then
		
			self:EmitCreepySounds()
		
		elseif EMITSOUND_PROB + STAB_PROB > randnum then
		
			self:StabbyLunge( Vec )
		
		end
	
	end

end


function ENT:Think()

	if not ValidEntity( self.Victim ) or not self.Victim:Alive() then return end

	-- OHSHI.. he sees us!
	if self:CanVictimSeeUs() then
		
		-- Pretend like nothing happened
		self:PlayDead()
		self:NextThink( CurTime() + COOLDOWN_TIME )

	else
	
		self:BeCreepy()
		self:NextThink( CurTime() + THINK_DELAY )
		
	end
	
	return true
	
end


function ENT:OnRemove()

	self:KillSounds()

end


